// -*-mode:c++; coding:utf-8-*-

#ifndef _COMPRESSOR_BMZ_HPP_
#define _COMPRESSOR_BMZ_HPP_

#include <unistd.h>

#include "wrapper.hpp"

namespace {
#include "bmz/bmz.h"
}

namespace xbase
{
	namespace compressor
	{
		struct bmz
		{
			enum default_args
			{
				ARG_OFFSET = 0,
				ARG_FP_LEN = 19
			};

			// return - zlen if success, or nagetive number
			template<typename Buffer>
			static inline
			ssize_t compress(Buffer &buffer,
					      const boost::asio::const_buffer &data) {
				assert(bmz_init() == BMZ_E_OK); // 必须检查是否满足bmz的计算环境

				const uint32_t &in_size =
					boost::asio::buffer_size(data);
				const std::size_t max_out_size = bmz_pack_buflen(in_size) + sizeof(uint32_t); /*for header length*/
				const std::size_t workmem_size = bmz_pack_worklen(in_size, ARG_FP_LEN);

				boost::asio::mutable_buffer out = buffer.prepare(max_out_size + workmem_size);
				boost::asio::mutable_buffer workmem_buf = out + max_out_size;
				char *workmem = boost::asio::buffer_cast<char*>(workmem_buf);

				integer_wrapper<uint32_t, kb::network_byteorder> *in_size_wrapper = reinterpret_cast<integer_wrapper<uint32_t, kb::network_byteorder> *>(boost::asio::buffer_cast<char*>(out));
				in_size_wrapper->set(in_size);
				out = out + in_size_wrapper->bytes();

				std::size_t real_out_size = max_out_size;
				const int ret = bmz_pack(boost::asio::buffer_cast<const char*>(data),
							 in_size,
							 boost::asio::buffer_cast<char*>(out),
							 &real_out_size,
							 ARG_OFFSET,
							 ARG_FP_LEN,
							 0,
							 workmem);

				if(ret != BMZ_E_OK)
				{
					return -1;
				}

				real_out_size += in_size_wrapper->bytes();
				buffer.commit(real_out_size);
				return real_out_size;
			}

			// return - zlen if success, or nagetive number
			template<typename Buffer>
			static inline
			ssize_t decompress(Buffer &buffer,
						const boost::asio::const_buffer &data) {
				assert(bmz_init() == BMZ_E_OK); // 必须检查是否满足bmz的计算环境
				
				if(boost::asio::buffer_size(data) < sizeof(uint32_t))
				{
					return -1;
				}
				
				const integer_wrapper<uint32_t, kb::network_byteorder> *expect_out_size_wrapper = reinterpret_cast<const integer_wrapper<uint32_t, kb::network_byteorder> *>(boost::asio::buffer_cast<const char *>(data));
				const uint32_t out_size = expect_out_size_wrapper->get();
				const uint32_t workmem_size = bmz_unpack_worklen(out_size);
				boost::asio::mutable_buffer out = buffer.prepare(out_size + workmem_size);
				boost::asio::mutable_buffer workmem_buf = out + out_size;
				char *workmem = boost::asio::buffer_cast<char*>(workmem_buf);

				const boost::asio::const_buffer real_data =
					data + expect_out_size_wrapper->bytes();
				std::size_t real_out_size = out_size;
				const int ret = bmz_unpack(boost::asio::buffer_cast<const char*>(real_data),
							   boost::asio::buffer_size(real_data),
							   boost::asio::buffer_cast<char*>(out),
							   &real_out_size,
							   workmem);
				if(ret != BMZ_E_OK)
				{
					return -1;
				}
				assert(out_size == real_out_size);
				buffer.commit(real_out_size);
				return real_out_size;
			}
		};

		// return - zlen if success, or nagetive number
		//          -1 for input data error;
		//          -2 for output buffer too small
		template<>
		inline
		ssize_t bmz::decompress(const boost::asio::mutable_buffer &buffer,
					     const boost::asio::const_buffer &data)
		{
			assert(bmz_init() == BMZ_E_OK); // 必须检查是否满足bmz的计算环境
			
			if(boost::asio::buffer_size(data) < sizeof(uint32_t))
			{
				return -1;
			}

			const integer_wrapper<uint32_t, kb::network_byteorder> *expect_out_size_wrapper = reinterpret_cast<const integer_wrapper<uint32_t, kb::network_byteorder> *>(boost::asio::buffer_cast<const char *>(data));

			const uint32_t out_size = expect_out_size_wrapper->get();
			if(boost::asio::buffer_size(buffer) < out_size) // not enough
			{
				return -1;
			}

			char *workmem = 0;
			bool is_allocated = false;
			const uint32_t workmem_size = bmz_unpack_worklen(out_size);
			if(boost::asio::buffer_size(buffer) >= (out_size + workmem_size))
			{
				workmem = boost::asio::buffer_cast<char*>(buffer) + out_size;
			}
			else
			{
				workmem = ::new char[workmem_size];
				is_allocated = true;
			}

			const boost::asio::const_buffer real_data =
				data + expect_out_size_wrapper->bytes();
			std::size_t real_out_size = out_size;
			char *dest = boost::asio::buffer_cast<char*>(buffer);
			const char *src = boost::asio::buffer_cast<const char*>(real_data);
			const int ret = bmz_unpack(src,
						   boost::asio::buffer_size(real_data),
						   dest,
						   &real_out_size,
						   workmem);
			if(ret != BMZ_E_OK)
			{
				return -1;
			}
			return real_out_size;
		}
	} // namespace compressor

} // namespace xbase

#endif	// _COMPRESSOR_BMZ_HPP_
